Roadmap buildout: rate limiting, history, drag-drop, onboarding, admin#48
Conversation
Adds the next chunk of the SpectraCleanse roadmap across three phases.
Infrastructure
- Add express-rate-limit: 60/min/IP global on /api/* (excluding /api/health
and the Stripe webhook), 10/min on /api/login, /api/register, /api/auth/*.
429 responses return JSON { error: "Too many requests" }.
- Expand /api/health to return { status, uptime, version, time } and document
it (plus rate limiting) in the README.
- Serve /sitemap.xml and /robots.txt from Express so they work in dev and
prod without depending on Vite's static pipeline; both reference the
canonical https://spectracleanse.com domain.
- Generate public/assets/og-image.png (1200x630) via a sharp-backed script
(scripts/generate-og-image.js) so social previews finally have an image.
- Add unit tests for cleansePolicy and processor (buildMetaToWrite,
detectMarkers, verifyFinalState, buildQualityVerification,
formatQuickTimeTimestamp). 28/28 tests pass.
Core feature / UX
- Reject WAV/FLAC at the Multer fileFilter stage with a 415 and a clear
message ("WAV and FLAC are not yet supported. Supported formats: MP3,
MP4, M4A.") instead of wasting bandwidth and 422-ing post-upload. Keep
ALLOWED_MIME in sync with CLEANSE_POLICY. Tighten the frontend accept=
attribute and validExt regex to match.
- Add GET /api/jobs (requireAuth, last 50) and a history modal in app.tsx
showing file, date, status pill, markers. New jobs columns
forensic_status + markers_removed (additive migration) are populated by
both /api/process and /api/process-batch.
- Add drag-and-drop on the main panel with a cyan ring/background
highlight; drops go through the same addFiles validation path.
- Replace the generic "Upgrade to continue" message (both the 402 detail
and the client-side fallback) with the spec'd copy referencing the
Creator plan price.
- Rework the results card with a big green "X AI markers removed - your
file is clean." headline, a status pill driven by X-Forensic-Status
(Clean / Clean with Notes / Review Required), Copy-Link / Tweet-This
share buttons, and a free-plan-only Creator upgrade nudge.
- Fix a latent bug: server.js called crypto.randomUUID() in
/api/process-batch without importing crypto.
Onboarding + admin
- Add a three-step first-run onboarding modal (Strip AI Fingerprints ->
How It Works -> You're Ready) gated by localStorage.onboarding_seen
with a Sparkles button in the navbar to re-show it.
- Add an admin health dashboard behind a requireAdmin middleware backed
by ADMIN_SECRET (bearer token, never logged): /admin/health,
/admin/recent-failures, /admin/usage-stats, and an inline HTML /admin
page (no JS framework) with uptime, DB counts, today's processing
volume, plan distribution bars, and a recent-failures table. None of
the admin routes expose user PII.
Constraints preserved
- express.raw() for the Stripe webhook still comes before express.json().
- exiftool.end() exit handler unchanged.
- requireAuth re-reads plan from DB; no caching of JWT plan field.
- SQLite WAL mode pragma untouched; jobs schema changes are additive.
- ALLOWED_MIME and CLEANSE_POLICY are kept in sync.
- No JWT_SECRET / STRIPE_WEBHOOK_SECRET / GEMINI_API_KEY / ADMIN_SECRET
in logs or responses.
Verification
- npm run test:run: 28/28 passing
- npx tsc --noEmit: clean
- node --check server.js: ok
- npm run build: production bundle builds, og-image.png ships in dist/
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reviewer's GuideImplements rate limiting and health/SEO endpoints on the backend, extends the jobs schema and processing pipeline to track forensic status/history, adds first‑run onboarding and drag‑and‑drop UX on the frontend, introduces an admin health dashboard, tightens media-type handling (WAV/FLAC rejection, MP3/MP4/M4A alignment), and adds tests plus an OG image generator script. Sequence diagram for fetching and displaying job history via GET /api/jobssequenceDiagram
actor User
participant App_tsx as App
participant Server_js as Server
participant requireAuth as requireAuth
participant DB as Database
User->>App_tsx: Click history button
App_tsx->>App_tsx: setHistoryOpen(true)
App_tsx->>Server_js: GET /api/jobs
Server_js->>requireAuth: requireAuth(req)
requireAuth-->>Server_js: userId from JWT
Server_js->>DB: SELECT id, filename, platform,
Server_js->>DB: forensic_status, markers_removed,
Server_js->>DB: created_at FROM jobs
DB-->>Server_js: rows (max 50)
Server_js-->>App_tsx: 200 { jobs: rows }
App_tsx->>App_tsx: setHistory(jobs)
App_tsx->>User: Render history modal with
App_tsx->>User: filename, date, status pill,
App_tsx->>User: markers_removed
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="app.tsx" line_range="1107-1116" />
<code_context>
+ ),
+ footnote: 'Your audio is never stored. Files are processed in memory and immediately deleted.',
+ },
+ {
+ title: "You're Ready",
+ body: 'You have 3 free cleanses this month. No credit card required.',
+ visual: null as any,
</code_context>
<issue_to_address>
**suggestion:** Avoid using `null as any` for the onboarding step `visual` field by making the property optional instead.
Since only the last step omits `visual`, you can update the step type to something like `{ title: string; body: string; visual?: React.ReactNode; footnote?: string }` and render it with a guard, e.g. `{current.visual && current.visual}`. This keeps the types accurate and removes the need for the unsafe `as any` cast.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
CI: bump exiftool-vendored ^28.3.1 -> ^35.20.0 and nodemailer ^6.10.1 -> ^8.0.9 to clear two high-severity advisories (GHSA-cw26-7653-2rp5, GHSA-mm7p-fcc7-pg87 / -rcmh-qjqh-p98v / -c7w3-x93f-qmm8 / -vvjj-xcjg-gr5g). Our usage is limited to exiftool.{read,write,end,version,readRaw} and nodemailer.{createTransport,sendMail} — both stable across the bump and verified by smoke-importing each module. `npm audit --audit-level=high` now exits 0 (7 moderate vulns remain in dev-only tooling, below the CI threshold). Code review: type the onboarding step array as `{ title; body; visual?; footnote? }[]` and drop the `null as any` cast + `'footnote' in current` guard. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…fig diagnostics - Dockerfile runtime stage was missing the server/ directory, causing a module-not-found crash on boot (server.js requires ./server/*). - Frontend no longer hard-throws when VITE_API_URL is unset; empty base URL now correctly means same-origin requests for single-service deployments. - Add startup [Config] log summary so Stripe/email live-vs-mock state is visible in logs. - Add render.yaml blueprint and RENDER.md with the full env-var reference.
Resolves GHSA-c7w3-x93f-qmm8 (SMTP command injection) and related high-severity advisories flagged by 'npm audit --audit-level=high' in CI. The createTransport/sendMail API used by server/emailService.js is unchanged; server smoke test still boots and /api/health responds.
Resolves GHSA-cw26-7653-2rp5 (argument injection via newline in tag names), the last high-severity advisory failing 'npm audit --audit-level=high' in CI. The read/write/version/readRaw/end API used by server/processor.js is unchanged; verified end-to-end: write+read+full -all= wipe cycle works on MP4 (bundled ExifTool 13.59), all 13 unit tests pass, and the server smoke test boots.
…dout/full-codebase-roadmap # Conflicts: # package-lock.json # package.json
|
Merged the Render deployment + security fixes from #49 into this branch (merge commit
Post-merge verification: 28/28 tests pass, Note: the Generated by Claude Code |
Summary
Implements the next chunk of the SpectraCleanse roadmap across three phases — infrastructure, core UX, and onboarding/admin — while preserving the project's critical constraints (Stripe webhook ordering, ExifTool cleanup, JWT plan freshness, SQLite WAL, ALLOWED_MIME ↔ CLEANSE_POLICY sync, secret hygiene).
What's new
Infrastructure
express-rate-limit: 60/min/IP on/api/*(excluding/api/healthand the Stripe webhook), 10/min on/api/login,/api/register,/api/auth/*. 429 → JSON{ error: "Too many requests" }./api/healthnow returns{ status, uptime, version, time }; documented in README alongside the new rate limiting./sitemap.xmland/robots.txtso they work in dev and prod without depending on Vite's static pipeline; both usehttps://spectracleanse.com.public/assets/og-image.png(1200×630) generated viascripts/generate-og-image.js(sharp). Already wired up inindex.html.cleansePolicyandprocessor(buildMetaToWrite, detectMarkers, verifyFinalState, buildQualityVerification, formatQuickTimeTimestamp). 28/28 tests pass.Core feature / UX
fileFilterwith a clean 415 ({ error: "WAV and FLAC are not yet supported. Supported formats: MP3, MP4, M4A." }) instead of 422-ing after upload. Frontendaccept=andvalidExtupdated to match.GET /api/jobs+ history modal inapp.tsxshowing file / date / status pill / markers, gated byrequireAuth. Additive migration addsforensic_statusandmarkers_removedcolumns tojobsand both single- and batch-process endpoints populate them.detailand the client fallback) with the spec'd Creator-plan wording.X-Forensic-Status, Copy-Link / Tweet-This share buttons, and a free-plan-only Creator upgrade nudge.server.jscalledcrypto.randomUUID()in/api/process-batchwithout importingcrypto.Onboarding + admin
app.tsx(Strip AI Fingerprints → How It Works → You're Ready), gated bylocalStorage.onboarding_seen. A new ✨ button in the navbar re-shows it.requireAdminmiddleware that checksADMIN_SECRETas a bearer token (never logged):GET /admin/health,/admin/recent-failures,/admin/usage-stats, plus an inline HTML/adminpage (no JS framework) with uptime, DB counts, today's volume, plan distribution bars, and recent failures. No user PII in any response.ADMIN_SECRET=added to.env.examplewith a hint for generating one.Constraints preserved
express.raw()for the Stripe webhook still comes beforeexpress.json().exiftool.end()exit handler unchanged.requireAuthstill re-reads plan from the DB; no JWT plan caching.ALTER TABLE.ALLOWED_MIMEandCLEANSE_POLICYkept in sync (mp3 quick, mp4/m4a server).Test plan
npm run test:run— 28/28 passingnpx tsc --noEmit— cleannode --check server.js— oknpm run build— production bundle builds,og-image.pngships indist/assets//api/health,/sitemap.xml,/robots.txt,/api/jobs, drag-and-drop, onboarding first-run, history modal, results-card social proof.wav→ expect 415 with the new messageADMIN_SECRETand visit/admin— confirm dashboard renders and 401s without bearer tokenNeeds human action
STRIPE_CREATOR_PRICE_ID,STRIPE_STUDIO_PRICE_ID) on Render — unchanged.ADMIN_SECRETmust be set in Render before/admin/*is reachable.🤖 Generated with Claude Code
Summary by Sourcery
Implement rate limiting, richer health/SEO endpoints, onboarding and admin dashboards, upload/history UX improvements, and supporting tests/assets for SpectraCleanse.
New Features:
Bug Fixes:
Enhancements:
Build:
Documentation:
Tests: